// Copyright 2024 Administrator
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { Delete, Get, Post, Put } from "@/http/http";
import axios, { AxiosProgressEvent } from "axios";
import {
  UploadProgressEvent,
  UploadRequestHandler,
  UploadRequestOptions,
} from "element-plus";
import { UploadAjaxError } from "element-plus/es/components/upload/src/ajax";

import { isNil } from "lodash";
import { FileType, ProgressFunc, UploadFileDto } from "./type";

/**
 * 上传图片
 *
 * @param queryParams
 */
export function uploadPic(file: File): Promise<any> {
  const data = new FormData();
  data.append("file", file);
  data.append("isPublic", JSON.stringify(true));
  return Post<any, string>("/api/v1/minio", data);
}
/**
 * 上传视频
 *
 * @param queryParams
 */
export function uploadVideo(file: File): Promise<any> {
  const data = new FormData();
  data.append("file", file);
  return Post<any, string>("/api/v1/minio/video", data);
}
/**
 * 获取直传地址
 *
 * @param queryParams
 */
export function getPresignedUrl(queryParams: UploadFileDto): Promise<any> {
  return Get<UploadFileDto, string>(
    "/api/v1/minio/getPresignedUrl/",
    queryParams
  );
}
/**
 * 获取直传地址
 *
 * @param queryParams
 */
export function getPresignedUrlByName(
  fileName: string,
  isPublic: boolean = true
): Promise<any> {
  return Get<{ fileName: string; isPublic: boolean }, any>(
    "/api/v1/minio/getPresignedUrlByName",
    { fileName, isPublic }
  );
}

/**
 * 删除minio文件
 *
 * @param queryParams
 */
export function removeMinioFile(o: UploadFileDto): Promise<string> {
  return Delete<UploadFileDto, string>("/api/v1/minio/deleteFile", o);
}

/**
 * 删除minio文件
 *
 * @param queryParams
 */
export function updateMinioFile(o: UploadFileDto): Promise<string> {
  return Put<UploadFileDto>("/api/v1/minio/updateFile", o);
}

/**
 * 批量上传文件
 *
 * @param queryParams
 */

export async function batchUpload(
  params: FileType[],
  progress: ProgressFunc
): Promise<any> {
  const querys = params.map<Promise<any>>((param, index) => {
    const { file } = param;
    if (!file) {
      throw new Error("缺少文件");
    }
    let fileName = param?.name ?? file?.name;
    if (!fileName) {
      throw new Error("缺少文件名称");
    }

    return (async () => {
      const { url } = await getPresignedUrlByName(fileName);

      return await axios.put(url, file, {
        headers: { "Content-Type": file?.type },
        onUploadProgress: (e: AxiosProgressEvent) => {
          progress(e.loaded, e!.total!, index);
        },
      });
    })();
  });

  return await Promise.all(querys);
}

export async function pureUpload(
  url: string,
  file: File,
  progress: ProgressFunc
) {
  return await axios.put(url, file, {
    headers: { "Content-Type": file?.type },
    onUploadProgress: (e: AxiosProgressEvent) => {
      progress(e.loaded, e!.total!);
    },
  });
}

/**
 * 上传文件
 * @param param
 * @param progress
 * @returns
 */
export function uploadByName(param: FileType, progress: ProgressFunc) {
  const { file } = param;
  if (!file) {
    throw new Error("缺少文件");
  }

  let fileName = param?.name ?? file?.name;
  if (!fileName) {
    throw new Error("缺少文件名称");
  }
  return (async () => {
    const { url } = await getPresignedUrlByName(fileName);

    return await axios.put(url, file, {
      headers: { "Content-Type": file?.type },
      onUploadProgress: (e: AxiosProgressEvent) => {
        progress(e.loaded, e!.total!);
      },
    });
  })();
}

/**
 * 上传文件
 * @param param
 * @param progress
 * @returns
 */
export async function uploadFile(
  param: UploadFileDto,
  progress: ProgressFunc
): Promise<UploadFileDto> {
  const { raw } = param;
  if (!raw) {
    throw new Error("缺少文件");
  }

  let fileName = param?.name ?? raw?.name;
  if (!fileName) {
    throw new Error("缺少文件名称");
  }

  const { url, fileId } = await getPresignedUrl(param);

  await axios.put(url, raw, {
    headers: { "Content-Type": raw?.type },
    onUploadProgress: (e: AxiosProgressEvent) => {
      progress(e.loaded, e!.total!);
    },
  });

  return { ...param, url: url.split("?").at(0), id: fileId };
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const SCOPE = "ElUpload";

function getError(
  action: string,
  option: UploadRequestOptions,
  xhr: XMLHttpRequest
) {
  let msg: string;
  if (xhr.response) {
    msg = `${xhr.response.error || xhr.response}`;
  } else if (xhr.responseText) {
    msg = `${xhr.responseText}`;
  } else {
    msg = `fail to ${option.method} ${action} ${xhr.status}`;
  }

  return new UploadAjaxError(msg, xhr.status, option.method, action);
}

function getBody(xhr: XMLHttpRequest): XMLHttpRequestResponseType {
  const text = xhr.responseText || xhr.response;
  if (!text) {
    return text;
  }

  try {
    return JSON.parse(text);
  } catch {
    return text;
  }
}

export const ajaxUpload: UploadRequestHandler = (option) => {
  if (typeof XMLHttpRequest === "undefined")
    console.error(SCOPE, "XMLHttpRequest is undefined");

  const xhr = new XMLHttpRequest();
  const action = option.action;

  if (xhr.upload) {
    xhr.upload.addEventListener("progress", (evt) => {
      const progressEvt = evt as UploadProgressEvent;
      progressEvt.percent = evt.total > 0 ? (evt.loaded / evt.total) * 100 : 0;
      option.onProgress(progressEvt);
    });
  }

  const formData = new FormData();
  if (option.data) {
    for (const [key, value] of Object.entries(option.data)) {
      // if (isArray(value) && value.length) formData.append(key, ...value)
      // else formData.append(key, value)
    }
  }
  formData.append(option.filename, option.file, option.file.name);

  xhr.addEventListener("error", () => {
    option.onError(getError(action, option, xhr));
  });

  xhr.addEventListener("load", () => {
    if (xhr.status < 200 || xhr.status >= 300) {
      return option.onError(getError(action, option, xhr));
    }
    option.onSuccess(getBody(xhr));
  });

  xhr.open(option.method, action, true);

  if (option.withCredentials && "withCredentials" in xhr) {
    xhr.withCredentials = true;
  }

  const headers = Object.assign(option.headers, {
    "Content-Type": option.file?.type,
  }); //option.headers || { 'Content-Type': option.file?.type }
  if (headers instanceof Headers) {
    headers.forEach((value, key) => xhr.setRequestHeader(key, value));
  } else {
    for (const [key, value] of Object.entries(headers)) {
      if (isNil(value)) continue;
      xhr.setRequestHeader(key, String(value));
    }
  }

  xhr.send(formData);
  return xhr;
};

export const axiosUpload: UploadRequestHandler = (options) => {
  const {
    action,
    file,
    filename,
    data,
    onError,
    method,
    headers,
    onProgress,
    onSuccess,
    withCredentials,
  } = options;

  return new Promise<unknown>((resolve, reject) => {
    axios
      .put(action, file, {
        headers: { "Content-Type": file?.type },
        onUploadProgress: (e: AxiosProgressEvent) => {
          if (!e.total) {
            return console.error("上传文件不为空!");
          }
          const percent: UploadProgressEvent = {
            percent: e.loaded / e!.total!,
          } as UploadProgressEvent;
          onProgress(percent);
        },
      })
      .then((res) => {
        onSuccess(res);
      })
      .catch((err) => {
        onError(err);
      });
  });
};
